/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.yonsei.iwb.smarttv.model.measure;

import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import edu.yonsei.iwb.smarttv.util.SMARTTVLog;
import java.io.*;
import java.util.*;

/**
 *
 * @author Onlyjiny
 */
public class WeightModel {

    private static final String DUMMY_ANNOTATION = "http://iweb.yonsei.ac.kr/ontologies/smarttv/isDummy";
    private Model model;
    private ResourceWeights weight;
    private ResourceWeights instancesWeight;
    private UncountedResources ur;
    // <-- ** 해당되는 프로퍼티의 해당되는 트리플을 저장하는 테이블.--> by johannes sohn
    private Hashtable<Property, PropertyWeightSet> weightTable;

    public WeightModel(Model model, int type) {
        this.model = model;
        this.ur = new UncountedResources();
        this.weightTable = new Hashtable<Property, PropertyWeightSet>();
        if (type == WeightModelType.MEMORY) {
            this.weight = new ResourceWeights();
            this.instancesWeight = new ResourceWeights();
            this.initialize();
        } else if (type == WeightModelType.FILE) {
            // File로부터 weightTable을 채울수 있는 메소드 추가.
            this.loadWeightTableFile();

        }
    }

    public void writeWeightTableFile() {
        try {

//            File dir = new File("E:/test");
//	    File file = new File(dir, fileName);

//            if (!dir.isDirectory()) {
//                dir.mkdir();
//	    }
            File file = new File(WeightModelType.FILEADDRESS);

//            if (!dir.isFile()) {
            file.createNewFile();
            FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            PrintWriter pw = new PrintWriter(bos);

            Enumeration<Property> e = this.weightTable.keys();
            while (e.hasMoreElements()) {
                Property key = e.nextElement();
                PropertyWeightSet pws = this.weightTable.get(key);
                for (int i = 0; i < pws.size(); i++) {
                    pw.write(pws.get(i).getDomain() + "," + pws.get(i).getProperty() + "," + pws.get(i).getRange() + "," + pws.get(i).getWeight());
                    pw.println();
                }
            }

            pw.flush();

//	    }

        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private List loadWeightTableFile() {
        // 여기에 파일 읽어서 weightTable에 저장하는 메소드 만들기

        FileReader fr;
	BufferedReader br;
	String getLine;
	Vector data = new Vector();
        Vector singlePropertyTriple = new Vector();
        
	try {
            SMARTTVLog.log(SMARTTVLog.DETAIL, "Load Weight Table from File \n");
            
            fr = new FileReader(WeightModelType.FILEADDRESS);
            br = new BufferedReader(fr);
            while((getLine = br.readLine()) != null){
                data.add(getLine);
            }
            br.close();
            fr.close();
            
            for (Iterator it = data.iterator(); it.hasNext();) {
                String pw =  (String) it.next();
                singlePropertyTriple.add(pw.toString().replace(",", "\n"));
                StringTokenizer st = new StringTokenizer(pw, ",");
                
                Resource domain = null;
                Property property = null;
                Resource range = null;
                double w = 0;
                for(int i = 1; st.hasMoreElements(); i++){
                    if(i == 1){
                        domain = ResourceFactory.createResource(st.nextToken());                        
                    } else if(i == 2){
                        property = ResourceFactory.createProperty(st.nextToken());                        
                    } else if(i == 3){
                        range = ResourceFactory.createResource(st.nextToken());                        
                    }else if(i == 4){
                        w = Double.valueOf(st.nextToken());                        
                    }
                }                
                if(weightTable.containsKey(property)){
                    PropertyWeightSet pws = weightTable.get(property);
                    pws.add(new PropertyWeight(domain, property, range, w));
                }else{
                    PropertyWeightSet pws = new PropertyWeightSet();
                    pws.add(new PropertyWeight(domain, property, range, w));
                    weightTable.put(property, pws);
                }
                
            }            
        } catch (IOException e) {
	    e.printStackTrace();
	}
        return data;
    }

//    public WeightModel(Model model) {
//        this.model = model;
//        ur = new UncountedResources();
//        this.weight = new ResourceWeights();
//        this.instancesWeight = new ResourceWeights();
//        this.initialize();
//    }
    private void initialize() {
//        this.initializeClassWeights();
        this.initializePropertyWeights(OWL.ObjectProperty);
//      this.initializePropertyWeights(OWL.DatatypeProperty);

    }

    private void initializeClassWeights() {
        Vector<Resource> dummyClasses = new Vector<Resource>();
        ResIterator dummies = this.model.listResourcesWithProperty(ResourceFactory.createProperty(WeightModel.DUMMY_ANNOTATION), true);
        while (dummies.hasNext()) {
            Resource dummy = dummies.nextResource();
            dummyClasses.add(dummy);
        }
        Vector<Resource> empty = new Vector<Resource>();
        int countOfInstances = 0;
        ResourceWeights tempClass = new ResourceWeights();
        ResIterator ri = this.model.listSubjectsWithProperty(RDF.type, OWL.Class);
        while (ri.hasNext()) {
            Resource classResource = ri.next();
            if (!dummyClasses.contains(classResource) && classResource.isURIResource()) {
                ResIterator instancesOfClass = this.model.listSubjectsWithProperty(RDF.type, classResource);
                int count = this.count(instancesOfClass);
                if (count == 0) {
                    empty.add(classResource);
                } else {
                    countOfInstances += count;
                    tempClass.put(classResource, (double) count);
                }
            }
        }
        this.update(tempClass, countOfInstances);
        for (int i = 0; i < dummyClasses.size(); i++) {
            this.weight.put(dummyClasses.get(i), 0.5);
        }
        this.updateEmptySet(empty);
        this.initializeInstancesWeights(tempClass);
    }

    private void initializeInstancesWeights(ResourceWeights tempClass) {
        int countOfInstances = 0;
        ResourceWeights tempInstances = new ResourceWeights();
        Vector<Resource> instances = new Vector<Resource>();
        for (int i = 0; i < this.ur.size(); i++) {
            Resource r = this.ur.get(i);
            if (tempClass.containsKey(r)) {
                tempClass.remove(this.ur.get(i));
            }
        }
        Enumeration<Resource> keys = tempClass.keys();
        while (keys.hasMoreElements()) {
            ResIterator instancesOfClass = this.model.listSubjectsWithProperty(RDF.type, keys.nextElement());
            while (instancesOfClass.hasNext()) {
                Resource instance = instancesOfClass.nextResource();
                if (!instances.contains(instance)) {
                    instances.add(instance);
                }
            }
        }
        for (int i = 0; i < instances.size(); i++) {
            Resource instance = instances.get(i);
            StmtIterator triplesAsSubject = this.model.listStatements(instance, null, (RDFNode) null);
            int count = this.count(triplesAsSubject);
            StmtIterator triplesAsObject = this.model.listStatements(null, null, (RDFNode) instance);
            count += this.count(triplesAsObject);
            countOfInstances += count;
            tempInstances.put(instance, (double) count);
        }
        keys = tempInstances.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            double value = tempInstances.get(key);
            tempInstances.put(key, 1 - (value / countOfInstances));
        }
        this.instancesWeight.putAll(Normalization.normalizeByMinMax(tempInstances));
    }

    private void initializePropertyWeights(Resource targetProperty) {

        ResIterator ri = this.model.listSubjectsWithProperty(RDF.type, targetProperty);
        Resource prop = null;
        while (ri.hasNext()) {
            prop = ri.next();
            Property property = ResourceFactory.createProperty(prop.getURI());

            PropertyWeightSet pws = new PropertyWeightSet();
            NodeIterator ni1 = this.model.listObjectsOfProperty(prop, RDFS.domain);

            while (ni1.hasNext()) {
                Resource domain = ni1.nextNode().asResource();

                NodeIterator ni2 = this.model.listObjectsOfProperty(prop, RDFS.range);
                while (ni2.hasNext()) {
                    Resource range = ni2.nextNode().asResource();
                    double w = this.getTripleWeightOfProperty(domain, property, range);
                    System.out.println(domain.getLocalName() + "  " + property.getLocalName() + "  " + range.getLocalName() + " === " + w);
                    pws.add(new PropertyWeight(domain, property, range, w));


                    ResIterator ri2 = this.model.listResourcesWithProperty(RDFS.subClassOf, range);
                    while (ri2.hasNext()) {
                        // range의 subclass들로 향하는 방향
                        Resource subclassOfr = ri2.nextResource();
                        if (!subclassOfr.equals(range)) {
                            double w1 = this.getTripleWeightOfProperty(domain, property, subclassOfr);
                            System.out.println(domain.getLocalName() + "  " + property.getLocalName() + "  " + subclassOfr.getLocalName() + " === " + w1);
                            pws.add(new PropertyWeight(domain, property, subclassOfr, w1));
                        }
                    }

                    ResIterator ri1 = this.model.listResourcesWithProperty(RDFS.subClassOf, domain);
                    while (ri1.hasNext()) {
                        Resource subclassOfd = ri1.nextResource();
                        if (!subclassOfd.equals(domain)) {
                            ResIterator ri3 = this.model.listResourcesWithProperty(RDFS.subClassOf, range);
                            while (ri3.hasNext()) {
                                // subclass 간의 관계
                                Resource subclassOfr = ri3.nextResource();
                                double w1 = this.getTripleWeightOfProperty(subclassOfd, property, subclassOfr);
                                System.out.println(subclassOfd.getLocalName() + "  " + property.getLocalName() + "  " + subclassOfr.getLocalName() + " === " + w1);
                                pws.add(new PropertyWeight(subclassOfd, property, subclassOfr, w1));
                            }
                        }
                    }
                }
            }
            weightTable.put(property, pws);
        }
        Normalization.normalizeByMinMax(weightTable);
    }

    private double getTripleWeightOfProperty(Resource domain, Property property, Resource range) {

        double numerator = 0;
        double denominator = 0;

        ResIterator res1 = this.model.listResourcesWithProperty(RDF.type, domain);
        while (res1.hasNext()) {
            Resource dr = res1.nextResource();
            ResIterator res2 = this.model.listResourcesWithProperty(RDF.type, range);
            while (res2.hasNext()) {
                Resource rr = res2.nextResource();

                StmtIterator stmt = this.model.listStatements(dr, property, rr);
                StmtIterator stmt2 = this.model.listStatements(dr, null, rr);

                //if 인지while 인지 결정해야 함.
                while (stmt.hasNext()) {
                    Statement st = stmt.nextStatement();
                    numerator++;
                }
                while (stmt2.hasNext()) {
                    Statement st2 = stmt2.nextStatement();
                    denominator++;
                }
            }
        }

        if (denominator == 0 || numerator == 0) {
            System.out.println("numerator =  " + numerator + "  denominator = " + denominator);
            return 1.0;
        }
        System.out.println("numerator =  " + numerator + "  denominator = " + denominator);
        double w = numerator / denominator;
        
        return 1.0 - w;
    }

    public Hashtable<Property, PropertyWeightSet> getPropertyWeightTable() {
        return this.weightTable;
    }

    private void update(ResourceWeights weights, int totalCount) {
        for (int i = 0; i < this.ur.size(); i++) {
            Resource r = this.ur.get(i);
            if (weights.containsKey(r)) {
                double count = weights.get(r);
                totalCount -= count;
                weights.remove(ur.get(i));
            }
        }
        ResourceWeights temp = new ResourceWeights();
        Enumeration<Resource> keys = weights.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            double value = weights.get(key);
            temp.put(key, 1 - (value / totalCount));
        }
        this.weight.putAll(Normalization.normalizeByMinMax(temp));
    }

    private void updateEmptySet(@SuppressWarnings("UseOfObsoleteCollectionType") Vector<Resource> empty) {
        for (int i = 0; i < empty.size(); i++) {
            this.weight.put(empty.get(i), 1.0);
        }
    }

    private int count(ResIterator ri) {
        int result = 0;
        while (ri.hasNext()) {
            result++;
            ri.next();
        }
        return result;
    }

    private int count(StmtIterator si) {
        int result = 0;
        while (si.hasNext()) {
            result++;
            si.next();
        }
        return result;
    }

    public double getWeight(Resource head, Resource arc, Resource tail) {
        double result = 0.0;
        result = this.weight.get(head) + this.weight.get(arc) + this.weight.get(tail);
        if (result == 0.0) {
            return 1;
        } else {
            return result;
        }
    }

    public double getWeight(Resource arc) {
        if (!this.weight.containsKey(arc)) {
            return 1.0;
        }
        double result = 0.0;
        result = this.weight.get(arc);
        if (result == 0.0) {
            return 1.0;
        } else {
            return result;
        }
    }

    public double getWeight(Triple triple) {
        double result = 1.0;
        Property p = this.model.createProperty(triple.getPredicate().getURI());

        PropertyWeightSet pws = this.weightTable.get(p);
        
        for (int i = 0; i < pws.size(); i++) {
            PropertyWeight pw = pws.get(i);
            if (pw.getDomain().equals(this.model.createProperty(triple.getSubject().getURI()))) {
                if (pw.getRange().equals(this.model.createProperty(triple.getObject().getURI()))) {
                    return pw.getWeight();
                }
            }
        }

        return result;

    }

    public double getInstanceWeight(Resource r) {
        if (!this.instancesWeight.containsKey(r)) {
            return 1.0;
        }
        double result = 0.0;
        result = this.instancesWeight.get(r);
        if (result == 0.0) {
            return 1;
        } else {
            return result;
        }
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("::::: Weights of classes and properties\n");
        Enumeration<Resource> keys = this.weight.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            sb.append(key.getURI() + ": " + this.weight.get(key) + "\n");
        }
        sb.append("::::: Weights of instances\n");
        keys = this.instancesWeight.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            sb.append(key.getURI() + ": " + this.instancesWeight.get(key) + "\n");
        }
        return sb.toString();
    }
}
